/******************************************************
* Team WB
* Brett Cate, William Kim
* 
* ALU ADT Header
*******************************************************/

#include "alu.h"


/*
Creates an ALU, initializes registers, and returns a pointer to the ALU.

parameter (*the_status_word): Pointer to the status word register for the ALU.
parameter (*the_error): Pointer to the int value of the error code.

return value: Pointer to the ALU.
*/
ALUPtr alu_new(RegisterPtr the_status_word_ptr, int* the_error) {
	ALUPtr alu;
	if (the_status_word_ptr == NULL) {
		*the_error = NULL_POINTER_ERROR;
		return NULL;
	}
	alu = (ALUPtr) malloc(sizeof(ALU));
	if (alu == NULL) {
		*the_error = OUT_OF_MEM_ERROR;
		return NULL;
	}
	alu->reg_A = register_new();
	alu->reg_B = register_new();
	alu->reg_R = register_new();
	alu->reg_R2 = register_new();

	register_putRegValue(alu->reg_A,0);
	register_putRegValue(alu->reg_B,0);
	register_putRegValue(alu->reg_R,0);
	register_putRegValue(alu->reg_R2,0);
	alu->status_word_ptr = the_status_word_ptr;
	*the_error = NO_ERROR;
	return alu;
}


/*
Sets the given ALU's status word reference to the given Register pointer and returns
the error code for this function.

parameter (the_ALU): Pointer to the ALU whose status word reference is set.
parameter (*the_status_word): Pointer to the new status word register for the ALU.

return value: An int containing the value of the error code for this function.
*/
int alu_setSWReference(ALUPtr the_ALU, Register* the_status_word_ptr) {
	if (the_ALU == NULL || the_status_word_ptr == NULL) {
		return NULL_POINTER_ERROR;
	}
	the_ALU->status_word_ptr = the_status_word_ptr;
	return NO_ERROR;
}

/*
Clears the given ALU's status word flags.

parameter (the_ALU): Pointer to the ALU whose status word flags are 
cleared (set to 0000).

return value: An int containing the value of the error code for this function.
*/
int alu_clearFlags(ALUPtr the_ALU) {
	if (the_ALU == NULL) {
		return NULL_POINTER_ERROR;
	}
	*(the_ALU->status_word_ptr) = *(the_ALU->status_word_ptr) & 0x0FFF;
	return NO_ERROR;
}

/*
Sets the status word to the lower 4 bits of the given ushort and returns the error
code for this function. Does not override bits that are already set. Use alu_clearFlags(...)
before calling this function if you want that behavior.

parameter (the_ALU): Pointer to the ALU whose status word flags are set.
parameter (the_bitmask): The ushort bitmask used to set the status word, using the lowest
4 bits of the ushort.

return value: An int containing the value of the error code for this function.
*/
int alu_setFlags(ALUPtr the_ALU, ushort the_bitmask) {
	if (the_ALU == NULL) {
		return NULL_POINTER_ERROR;
	}

	// *(the_ALU->status_word_ptr) = *(the_ALU->status_word_ptr) & 0x0FFF;
	*(the_ALU->status_word_ptr) = *(the_ALU->status_word_ptr) | (the_bitmask << 12);
	return NO_ERROR;
}

/*
Sets the indicated register in the ALU to the given value and returns the 
error code for this function.

parameter (the_ALU): Pointer to the ALU whose register is set.
parameter (the_register_number): The register number whose value is set. Valid values
for register number are 0 (for Register A), 1 (for Register B), 2 (for Register R),
and 3 (for Register R2). All other values will return the appropriate error code.
parameter (the_value): The Register (a ushort) value to set the indicated register to.

return value: An int containing the error code for this function.
*/
int alu_setRegister(ALUPtr the_ALU, int the_register_number, Register the_value) {
	if (the_ALU == NULL) {
		return NULL_POINTER_ERROR;
	}
	if (the_register_number > REG_R2 || the_register_number < REG_A) {
		return INVALID_REG_NUM;
	}
	if (the_register_number == REG_A) {
		register_putRegValue(the_ALU->reg_A, the_value);
	} else if (the_register_number == REG_B) {
		register_putRegValue(the_ALU->reg_B, the_value);
	} else if (the_register_number == REG_R) {
		register_putRegValue(the_ALU->reg_R, the_value);
	} else {
		assert (the_register_number == REG_R2);
		register_putRegValue(the_ALU->reg_R2, the_value);
	}
	return NO_ERROR;
}

/*
Returns the value of the indicated register, either R (0) or R2 (1).

parameter (the_ALU): Pointer to the ALU whos register value is returned.
parameter (the_register_number): The register number of the register to return.
Either 0 for register R or 1 for register R2.
parameter (*the_error): Pointer to an int containing the value of the error
code for this function.

return value: A Register (ushort) containing the value of the indicated ALU register.
*/
Register alu_getResult(ALUPtr the_ALU, int the_register_number, int* the_error) {
	if (the_ALU == NULL) {
		*the_error = NULL_POINTER_ERROR;
		return 0;
	}
	if (the_register_number < REG_R || the_register_number > REG_R2) {
		*the_error = INVALID_REG_NUM;
		return 0;
	}
	if (the_register_number == REG_R) {
		*the_error = NO_ERROR;
		return register_getRegValue(the_ALU->reg_R, the_error);
	} else {
		assert (the_register_number == REG_R2);
		*the_error = NO_ERROR;
		return register_getRegValue(the_ALU->reg_R2, the_error);
	}
}

/*
Function for determining which ALU operation to perform.

parameter (the_ALU): Pointer to the ALU which executes the operation.
parameter (the_op): The operation to perform.

return value: An int containing the error code for this function.
*/
int alu_op(ALUPtr the_ALU,int the_op) {
	alu_clearFlags(the_ALU);
	switch (the_op) {
		case MOD_ADD_OP :
			return alu_ADD(the_ALU);
		case MOD_SUB_OP :
			return alu_SUB(the_ALU);
		case MOD_MUL_OP :
			return alu_MUL(the_ALU);
		case MOD_DIV_OP :
			return alu_DIV(the_ALU);
		case MOD_AND_OP :
			return alu_AND(the_ALU);
		case MOD_OR_OP :
			return alu_OR(the_ALU);
		case MOD_XOR_OP :
			return alu_XOR(the_ALU);
		case MOD_NOT_OP :
			return alu_NOT(the_ALU);
		default :
			printf("Not a valid operation.");
			return INVALID_MENU_OPT;
	}
}

/*
Calculates value of registers A + B and puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_ADD(ALUPtr the_ALU) {
	uchar reg_a_sign, reg_b_sign, result_sign;
	int error_code;
	ushort reg_a_value, reg_b_value;
	int result;
	if (the_ALU == NULL) {
		return NULL_POINTER_ERROR;
	}
	reg_a_sign = register_signOf(the_ALU->reg_A, &error_code);
	if (error_code)	// If statement executed if *error_code is not zero,
						// meaning there is an error
		return error_code;
	reg_b_sign = register_signOf(the_ALU->reg_B, &error_code);
	if (error_code)
		return error_code;
	reg_a_value = register_getRegValue(the_ALU->reg_A, &error_code);
	if (error_code)
		return error_code;
	reg_b_value = register_getRegValue(the_ALU->reg_B, &error_code);
	if (error_code)
		return error_code;

	result = reg_a_value + reg_b_value;

	error_code = register_putRegValue(the_ALU->reg_R, result);
	if (error_code)
		return error_code;
	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	if (reg_a_sign == reg_b_sign && (reg_a_sign != result_sign))
		alu_setFlags(the_ALU,O_FLAG);
	if (alu_isCarry(result))
		alu_setFlags(the_ALU,C_FLAG);
	return NO_ERROR;
}

/*
Calculates value of registers A - B and puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_SUB(ALUPtr the_ALU) {
	int sub_error;
	
	*(the_ALU->reg_B) = -*(the_ALU->reg_B);
	sub_error = alu_ADD(the_ALU);
	*(the_ALU->reg_B) = -*(the_ALU->reg_B);
	return sub_error;
}

/*
Calculates value of registers A * B and puts the resulting high order
word in the R register, and the low order word in the R2 register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_MUL(ALUPtr the_ALU) {
	int regA_val, regB_val, result, result_sign, mul_error;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;

	regA_val = *(the_ALU->reg_A);
	regB_val = *(the_ALU->reg_B);

	if (register_signOf(the_ALU->reg_A,&mul_error))
		regA_val = -0x00010000 + regA_val;
	if (register_signOf(the_ALU->reg_B,&mul_error))
		regB_val = -0x00010000 + regB_val;

	result = regA_val * regB_val;
	*(the_ALU->reg_R) = (result & HIGH_WORD_BITMASK) >> 16;
	*(the_ALU->reg_R2) = result & LOW_WORD_BITMASK;

	// unimplemented : possible overflow case (0x8000 * 0xFFFF, and vice versa)

	result_sign = register_signOf(the_ALU->reg_R2, &mul_error);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);
	
	return NO_ERROR;
}

/*
Calculates value of registers A / B and puts the quotient 
in the R register, and the remainder in the R2 register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_DIV(ALUPtr the_ALU) {
	int regA_val, regB_val, quotient, remainder, result_sign, error_code;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;

	regA_val = *(the_ALU->reg_A);
	regB_val = *(the_ALU->reg_B);

	if (register_signOf(the_ALU->reg_A,&error_code))
		regA_val = -0x00010000 + regA_val;
	if (register_signOf(the_ALU->reg_B,&error_code))
		regB_val = -0x00010000 + regB_val;

	quotient = regA_val / regB_val;
	remainder = regA_val % regB_val;

	*(the_ALU->reg_R) = quotient;
	*(the_ALU->reg_R2) = remainder;

	// unimplemented : possible overflow case (0x8000 / 0xFFFF)
	//				   and divide by 0 case

	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)quotient == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	return NO_ERROR;
}

/*
Calculates bitwise AND of registers A and B and puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_AND(ALUPtr the_ALU) {
	ushort result, result_sign;
	int error_code;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = *(the_ALU->reg_A) & *(the_ALU->reg_B);
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	return NO_ERROR;
}

/*
Calculates bitwise OR of registers A and B and puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_OR(ALUPtr the_ALU) {
	ushort result, result_sign;
	int error_code;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = *(the_ALU->reg_A) | *(the_ALU->reg_B);
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	return NO_ERROR;
}

/*
Calculates bitwise XOR (exclusive OR) of registers A and B and 
puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_XOR(ALUPtr the_ALU) {
	ushort result, result_sign;
	int error_code;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = *(the_ALU->reg_A) ^ *(the_ALU->reg_B);
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	return NO_ERROR;
}


/*
Calculates bitwise NOT of register A and puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_NOT(ALUPtr the_ALU) {
	ushort result, result_sign;
	int error_code;

	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = ~*(the_ALU->reg_A);
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);
	
	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);

	return NO_ERROR;
}


/*
Shifts the value of register A one bit to the left and
puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_SHL(ALUPtr the_ALU) {
	ushort result_sign;
	int result, error_code;


	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = *(the_ALU->reg_A) << 1;
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);

	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);
	if (alu_isCarry(result))
		alu_setFlags(the_ALU,C_FLAG);

	return NO_ERROR;
}

/*
Shifts the value of register A one bit to the right and
puts the result in the R register.

parameter (the_ALU): Pointer to the ALU which executes the operation.

return value: An int containing the error code for this function.
*/
int alu_SHR(ALUPtr the_ALU) {
	ushort result, result_sign;
	int error_code;


	if (the_ALU == NULL)
		return NULL_POINTER_ERROR;
	
	result = *(the_ALU->reg_A) >> 1;
	*(the_ALU->reg_R) = result;

	result_sign = register_signOf(the_ALU->reg_R, &error_code);

	if (result_sign)
		alu_setFlags(the_ALU,N_FLAG);
	else if ((ushort)result == 0)
		alu_setFlags(the_ALU,Z_FLAG);
	if (alu_isCarry(result))
		alu_setFlags(the_ALU,C_FLAG);

	return NO_ERROR;
}

/*
Determines if a value resulted in a carry bit.

parameter (the_result): The resulting value as 32 bits.

return value: TRUE if the 16th bit has a 1 in it; FALSE otherwise.
*/
int alu_isCarry(int the_result) {
	return (the_result & CARRY_BITMASK) >> 16;
}

/* End Program */
